//
//  TYZRouter.m
//  TYZKitExampDemo
//
//  Created by TangBin on 2019/2/20.
//  Copyright © 2019 TangBin. All rights reserved.
//

#import "TYZRouter.h"
#import <objc/runtime.h>


#define kTargetPre @"WDTarget_"
#define kActionPre @"action_"

@interface TYZRouter ()
@property (nonatomic, strong) NSMutableDictionary *cachedTarget;
@end

@implementation TYZRouter

+ (instancetype)sharedInstance
{
    static TYZRouter *route;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        route = [[TYZRouter alloc] init];
    });
    return route;
}

/**
 远程App调用入口
 
 scheme://[target]/[action]?[params]
 
 url sample:
 aaa://targetA/actionB?id=123
 
 @param url url description
 @param completion completion description
 @return return value description
 */
- (id)performActionUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion
{
    /*
     eg:aaa://targetA/actionB?id=123
     query=id=123
     host=targetA
     path=/actionB
     */
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    NSString *urlStr = [url query];
    for (NSString *param in [urlStr componentsSeparatedByString:@"&"])
    {
        NSArray *list = [param componentsSeparatedByString:@"="];
        if (list.count < 2)
        {
            continue;
        }
        [params setObject:[list lastObject] forKey:[list firstObject]];
    }
    
    // 这里这么写主要是出于安全考虑，防止黑客通过远程方式调用本地模块。这里的做法足以应对绝大多数场景，如果要求更加严苛，也可以做更加复杂的安全逻辑。
    NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
    if ([actionName hasPrefix:@"native"])
    {
        return @(NO);
    }
    
    // 这个demo针对URL的路由处理非常简单，就只是取对应的target名字和method名字，但这已经足以应对绝大部份需求。如果需要拓展，可以在这个方法调用之前加入完整的路由逻辑
    id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO];
    if (completion)
    {
        if (result)
        {
            completion(@{@"result":result});
        }
        else
        {
            completion(nil);
        }
    }
    return result;
}

/**
 本地u组件调用入口
 
 @param targetName targetName description
 @param actionName actionName description
 @param param param description
 @param shouldCacheTarget shouldCacheTarget description
 @return return value description
 */
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)param shouldCacheTarget:(BOOL)shouldCacheTarget
{
    // generate target
    NSString *targetClsStr = [NSString stringWithFormat:@"%@%@", kTargetPre, targetName];
    NSObject *target = self.cachedTarget[targetClsStr];
    if (target == nil)
    {
        Class targetCls = NSClassFromString(targetClsStr);
        target = [[targetCls alloc] init];
    }
    
    // generate action
    NSString *actionStr = [NSString stringWithFormat:@"%@%@:", kActionPre, actionName];
    SEL action = NSSelectorFromString(actionStr);
    if (target == nil)
    {
        // 这里是处理无响应请求的地方之一，这个demo做得比较简单，如果没有可以响应的target，就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上，然后处理这种请求的
        [self noFindTargetAction:actionStr target:targetClsStr param:param];
        return nil;
    }
    
    if (shouldCacheTarget)
    {
        self.cachedTarget[targetClsStr] = target;
    }
    
    if ([target respondsToSelector:action])
    {
        return [self safePerformTarget:target action:action param:param];
    }
    
    // 这里是处理无响应请求的地方，如果无响应，则尝试调用对应target的notFound方法统一处理
    action = NSSelectorFromString(@"notFound:");
    if ([target respondsToSelector:action])
    {
        return [self safePerformTarget:target action:action param:param];
    }
    
    // 这里也是处理无响应请求的地方，在notFound都没有的时候，这个demo是直接return了。实际开发过程中，可以用前面提到的固定的target顶上的。
    [self noFindTargetAction:actionStr target:targetClsStr param:param];
    [self.cachedTarget removeObjectForKey:targetClsStr];
    
    return nil;
}

/**
 清空具体的缓存
 
 @param targetName targetName description
 */
- (void)removeCachedTargetWithTargetName:(NSString *)targetName
{
    NSString *tartClsStr = [NSString stringWithFormat:@"%@%@", kTargetPre, targetName];
    [self.cachedTarget removeObjectForKey:tartClsStr];
}

#pragma mark - private methods
// 对象没有找到对应的方法，进入这个函数执行
- (void)noFindTargetAction:(NSString *)actionStr target:(NSString *)targetStr param:(NSDictionary *)originParams
{
    SEL action = NSSelectorFromString(@"Action_response:");
    NSObject *target = [[NSClassFromString(@"Target_NoTargetAction") alloc] init];
    
    NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithCapacity:3];
    params[@"originParams"] = originParams;
    params[@"targetStr"] = targetStr;
    params[@"actionStr"] = actionStr;
    
    [self safePerformTarget:target action:action param:params];
}

- (id)safePerformTarget:(NSObject *)target action:(SEL)action param:(NSDictionary *)param
{
    NSMethodSignature *methodSig = [target methodSignatureForSelector:action];
    if (methodSig == nil)
    {
        return nil;
    }
    
    const char *retType = [methodSig methodReturnType];
    
    if (strcmp(retType, @encode(void)) == 0)
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&param atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        return nil;
    }
    
    if (strcmp(retType, @encode(NSInteger)) == 0)
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&param atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(BOOL)) == 0)
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&param atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        BOOL result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(CGFloat)) == 0)
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&param atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        CGFloat result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
    if (strcmp(retType, @encode(NSUInteger)) == 0)
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&param atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSUInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [target performSelector:action withObject:param];
#pragma clang diagnostic pop
}

#pragma amrk - getters and setters
- (NSMutableDictionary *)cachedTarget
{
    if (_cachedTarget == nil)
    {
        _cachedTarget = [[NSMutableDictionary alloc] init];
    }
    return _cachedTarget;
}

@end






















